Skip to content

[LifetimeSafety] Implement dataflow analysis for loan propagation #147295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: users/usx95/lifetime-safety-initial
Choose a base branch
from

Conversation

usx95
Copy link
Contributor

@usx95 usx95 commented Jul 7, 2025

This patch introduces the core dataflow analysis infrastructure for the C++ Lifetime Safety checker. This change implements the logic to propagate "loan" information across the control-flow graph. The primary goal is to compute a fixed-point state that accurately models which pointer (Origin) can hold which borrow (Loan) at any given program point.

Key components

  • LifetimeLattice: Defines the dataflow state, mapping an OriginID to a LoanSet using llvm::ImmutableMap.

  • Transferer: Implements the transfer function, which updates the LifetimeLattice by applying the lifetime facts (Issue, AssignOrigin, etc.) generated for each basic block.

  • LifetimeDataflow: A forward dataflow analysis driver that uses a worklist algorithm to iterate over the CFG until the lattice state converges.

The existing test suite has been extended to check the final dataflow results.

This work is a prerequisite for the final step of the analysis: consuming these results to identify and report lifetime violations.

@usx95 usx95 force-pushed the users/usx95/lifetime-safety-add-dataflow branch from 0004422 to dd2dd83 Compare July 7, 2025 14:43
@usx95 usx95 self-assigned this Jul 7, 2025
@usx95 usx95 changed the title [LifetimeSafety] Propagate loans using dataflow analysis [LifetimeSafety] Implement dataflow analysis for loan propagation Jul 7, 2025
@usx95 usx95 force-pushed the users/usx95/lifetime-safety-add-dataflow branch from dd2dd83 to e870b04 Compare July 7, 2025 14:58
@usx95 usx95 requested review from Xazax-hun and ymand July 7, 2025 15:02
@usx95 usx95 marked this pull request as ready for review July 7, 2025 15:03
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:analysis labels Jul 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 7, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-analysis

Author: Utkarsh Saxena (usx95)

Changes

This patch introduces the core dataflow analysis infrastructure for the C++ Lifetime Safety checker. This change implements the logic to propagate "loan" information across the control-flow graph. The primary goal is to compute a fixed-point state that accurately models which pointer (Origin) can hold which borrow (Loan) at any given program point.

Key components

  • LifetimeLattice: Defines the dataflow state, mapping an OriginID to a LoanSet using llvm::ImmutableMap.

  • Transferer: Implements the transfer function, which updates the LifetimeLattice by applying the lifetime facts (Issue, AssignOrigin, etc.) generated for each basic block.

  • LifetimeDataflow: A forward dataflow analysis driver that uses a worklist algorithm to iterate over the CFG until the lattice state converges.

The existing test suite has been extended to check the final dataflow results.

This work is a prerequisite for the final step of the analysis: consuming these results to identify and report lifetime violations.


Patch is 20.87 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/147295.diff

2 Files Affected:

  • (modified) clang/lib/Analysis/LifetimeSafety.cpp (+257-1)
  • (modified) clang/test/Sema/warn-lifetime-safety-dataflow.cpp (+186)
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index 2c2309de90e26..e881e592ef59f 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -13,7 +13,10 @@
 #include "clang/Analysis/Analyses/PostOrderCFGView.h"
 #include "clang/Analysis/AnalysisDeclContext.h"
 #include "clang/Analysis/CFG.h"
+#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
 #include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/ImmutableMap.h"
+#include "llvm/ADT/ImmutableSet.h"
 #include "llvm/ADT/PointerUnion.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/Debug.h"
@@ -482,7 +485,247 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
 };
 
 // ========================================================================= //
-//  TODO: Run dataflow analysis to propagate loans, analyse and error reporting.
+//                              The Dataflow Lattice
+// ========================================================================= //
+
+// Using LLVM's immutable collections is efficient for dataflow analysis
+// as it avoids deep copies during state transitions.
+// TODO(opt): Consider using a bitset to represent the set of loans.
+using LoanSet = llvm::ImmutableSet<LoanID>;
+using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
+
+/// An object to hold the factories for immutable collections, ensuring
+/// that all created states share the same underlying memory management.
+struct LifetimeFactory {
+  OriginLoanMap::Factory OriginMapFact;
+  LoanSet::Factory LoanSetFact;
+
+  LoanSet createLoanSet(LoanID LID) {
+    return LoanSetFact.add(LoanSetFact.getEmptySet(), LID);
+  }
+};
+
+/// LifetimeLattice represents the state of our analysis at a given program
+/// point. It is an immutable object, and all operations produce a new
+/// instance rather than modifying the existing one.
+struct LifetimeLattice {
+  /// The map from an origin to the set of loans it contains.
+  /// TODO(opt): To reduce the lattice size, propagate origins of declarations,
+  /// not expressions, because expressions are not visible across blocks.
+  OriginLoanMap Origins = OriginLoanMap(nullptr);
+
+  explicit LifetimeLattice(const OriginLoanMap &S) : Origins(S) {}
+  LifetimeLattice() = default;
+
+  bool operator==(const LifetimeLattice &Other) const {
+    return Origins == Other.Origins;
+  }
+  bool operator!=(const LifetimeLattice &Other) const {
+    return !(*this == Other);
+  }
+
+  LoanSet getLoans(OriginID OID, LifetimeFactory &Factory) const {
+    if (auto *Loans = Origins.lookup(OID))
+      return *Loans;
+    return Factory.LoanSetFact.getEmptySet();
+  }
+
+  /// Computes the union of two lattices by performing a key-wise join of
+  /// their OriginLoanMaps.
+  // TODO(opt): This key-wise join is a performance bottleneck. A more
+  // efficient merge could be implemented using a Patricia Trie or HAMT
+  // instead of the current AVL-tree-based ImmutableMap.
+  LifetimeLattice join(const LifetimeLattice &Other,
+                       LifetimeFactory &Factory) const {
+    /// Merge the smaller map into the larger one ensuring we iterate over the
+    /// smaller map.
+    if (Origins.getHeight() < Other.Origins.getHeight())
+      return Other.join(*this, Factory);
+
+    OriginLoanMap JoinedState = Origins;
+    // For each origin in the other map, union its loan set with ours.
+    for (const auto &Entry : Other.Origins) {
+      OriginID OID = Entry.first;
+      LoanSet OtherLoanSet = Entry.second;
+      JoinedState = Factory.OriginMapFact.add(
+          JoinedState, OID,
+          join(getLoans(OID, Factory), OtherLoanSet, Factory));
+    }
+    return LifetimeLattice(JoinedState);
+  }
+
+  LoanSet join(LoanSet a, LoanSet b, LifetimeFactory &Factory) const {
+    /// Merge the smaller set into the larger one ensuring we iterate over the
+    /// smaller set.
+    if (a.getHeight() < b.getHeight())
+      std::swap(a, b);
+    LoanSet Result = a;
+    for (LoanID LID : b) {
+      /// TODO(opt): Profiling shows that this loop is a major performance
+      /// bottleneck. Investigate using a BitVector to represent the set of
+      /// loans for improved join performance.
+      Result = Factory.LoanSetFact.add(Result, LID);
+    }
+    return Result;
+  }
+
+  void dump(llvm::raw_ostream &OS) const {
+    OS << "LifetimeLattice State:\n";
+    if (Origins.isEmpty())
+      OS << "  <empty>\n";
+    for (const auto &Entry : Origins) {
+      if (Entry.second.isEmpty())
+        OS << "  Origin " << Entry.first << " contains no loans\n";
+      for (const LoanID &LID : Entry.second)
+        OS << "  Origin " << Entry.first << " contains Loan " << LID << "\n";
+    }
+  }
+};
+
+// ========================================================================= //
+//                              The Transfer Function
+// ========================================================================= //
+class Transferer {
+  FactManager &AllFacts;
+  LifetimeFactory &Factory;
+
+public:
+  explicit Transferer(FactManager &F, LifetimeFactory &Factory)
+      : AllFacts(F), Factory(Factory) {}
+
+  /// Computes the exit state of a block by applying all its facts sequentially
+  /// to a given entry state.
+  /// TODO: We might need to store intermediate states per-fact in the block for
+  /// later analysis.
+  LifetimeLattice transferBlock(const CFGBlock *Block,
+                                LifetimeLattice EntryState) {
+    LifetimeLattice BlockState = EntryState;
+    llvm::ArrayRef<const Fact *> Facts = AllFacts.getFacts(Block);
+
+    for (const Fact *F : Facts) {
+      BlockState = transferFact(BlockState, F);
+    }
+    return BlockState;
+  }
+
+private:
+  LifetimeLattice transferFact(LifetimeLattice In, const Fact *F) {
+    switch (F->getKind()) {
+    case Fact::Kind::Issue:
+      return transfer(In, *F->getAs<IssueFact>());
+    case Fact::Kind::AssignOrigin:
+      return transfer(In, *F->getAs<AssignOriginFact>());
+    // Expire and ReturnOfOrigin facts don't modify the Origins and the State.
+    case Fact::Kind::Expire:
+    case Fact::Kind::ReturnOfOrigin:
+      return In;
+    }
+    llvm_unreachable("Unknown fact kind");
+  }
+
+  /// A new loan is issued to the origin. Old loans are erased.
+  LifetimeLattice transfer(LifetimeLattice In, const IssueFact &F) {
+    OriginID OID = F.getOriginID();
+    LoanID LID = F.getLoanID();
+    return LifetimeLattice(
+        Factory.OriginMapFact.add(In.Origins, OID, Factory.createLoanSet(LID)));
+  }
+
+  /// The destination origin's loan set is replaced by the source's.
+  /// This implicitly "resets" the old loans of the destination.
+  LifetimeLattice transfer(LifetimeLattice InState, const AssignOriginFact &F) {
+    OriginID DestOID = F.getDestOriginID();
+    OriginID SrcOID = F.getSrcOriginID();
+    LoanSet SrcLoans = InState.getLoans(SrcOID, Factory);
+    return LifetimeLattice(
+        Factory.OriginMapFact.add(InState.Origins, DestOID, SrcLoans));
+  }
+};
+// ========================================================================= //
+//                              Dataflow analysis
+// ========================================================================= //
+
+/// Drives the intra-procedural dataflow analysis.
+///
+/// Orchestrates the analysis by iterating over the CFG using a worklist
+/// algorithm. It computes a fixed point by propagating the LifetimeLattice
+/// state through each block until the state no longer changes.
+/// TODO: Maybe use the dataflow framework! The framework might need changes
+/// to support the current comparison done at block-entry.
+class LifetimeDataflow {
+  const CFG &Cfg;
+  AnalysisDeclContext &AC;
+  LifetimeFactory LifetimeFact;
+
+  Transferer Xfer;
+
+  /// Stores the merged analysis state at the entry of each CFG block.
+  llvm::DenseMap<const CFGBlock *, LifetimeLattice> BlockEntryStates;
+  /// Stores the analysis state at the exit of each CFG block, after the
+  /// transfer function has been applied.
+  llvm::DenseMap<const CFGBlock *, LifetimeLattice> BlockExitStates;
+
+public:
+  LifetimeDataflow(const CFG &C, FactManager &FS, AnalysisDeclContext &AC)
+      : Cfg(C), AC(AC), Xfer(FS, LifetimeFact) {}
+
+  void run() {
+    llvm::TimeTraceScope TimeProfile("Lifetime Dataflow");
+    ForwardDataflowWorklist Worklist(Cfg, AC);
+    const CFGBlock *Entry = &Cfg.getEntry();
+    BlockEntryStates[Entry] = LifetimeLattice{};
+    Worklist.enqueueBlock(Entry);
+    while (const CFGBlock *B = Worklist.dequeue()) {
+      LifetimeLattice EntryState = getEntryState(B);
+      LifetimeLattice ExitState = Xfer.transferBlock(B, EntryState);
+      BlockExitStates[B] = ExitState;
+
+      for (const CFGBlock *Successor : B->succs()) {
+        auto SuccIt = BlockEntryStates.find(Successor);
+        LifetimeLattice OldSuccEntryState = (SuccIt != BlockEntryStates.end())
+                                                ? SuccIt->second
+                                                : LifetimeLattice{};
+        LifetimeLattice NewSuccEntryState =
+            OldSuccEntryState.join(ExitState, LifetimeFact);
+        // Enqueue the successor if its entry state has changed.
+        // TODO(opt): Consider changing 'join' to report a change if !=
+        // comparison is found expensive.
+        if (SuccIt == BlockEntryStates.end() ||
+            NewSuccEntryState != OldSuccEntryState) {
+          BlockEntryStates[Successor] = NewSuccEntryState;
+          Worklist.enqueueBlock(Successor);
+        }
+      }
+    }
+  }
+
+  void dump() const {
+    llvm::dbgs() << "==========================================\n";
+    llvm::dbgs() << "       Dataflow results:\n";
+    llvm::dbgs() << "==========================================\n";
+    const CFGBlock &B = Cfg.getExit();
+    getExitState(&B).dump(llvm::dbgs());
+  }
+
+  LifetimeLattice getEntryState(const CFGBlock *B) const {
+    auto It = BlockEntryStates.find(B);
+    if (It != BlockEntryStates.end()) {
+      return It->second;
+    }
+    return LifetimeLattice{};
+  }
+
+  LifetimeLattice getExitState(const CFGBlock *B) const {
+    auto It = BlockExitStates.find(B);
+    if (It != BlockExitStates.end()) {
+      return It->second;
+    }
+    return LifetimeLattice{};
+  }
+};
+
+// ========================================================================= //
+//  TODO: Analysing dataflow results and error reporting.
 // ========================================================================= //
 } // anonymous namespace
 
@@ -495,5 +738,18 @@ void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
   FactGenerator FactGen(FactMgr, AC);
   FactGen.run();
   DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
+
+  /// TODO(opt): Consider optimizing individual blocks before running the
+  /// dataflow analysis.
+  /// 1. Expression Origins: These are assigned once and read at most once,
+  ///    forming simple chains. These chains can be compressed into a single
+  ///    assignment.
+  /// 2. Block-Local Loans: Origins of expressions are never read by other
+  ///    blocks; only Decls are visible.  Therefore, loans in a block that
+  ///    never reach an Origin associated with a Decl can be safely dropped by
+  ///    the analysis.
+  LifetimeDataflow Dataflow(Cfg, FactMgr, AC);
+  Dataflow.run();
+  DEBUG_WITH_TYPE("LifetimeDataflow", Dataflow.dump());
 }
 } // namespace clang
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 4ca094a4393b0..8f4547e77681a 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -18,6 +18,10 @@ MyObj* return_local_addr() {
 // CHECK:   ReturnOfOrigin (OriginID: [[O_RET_VAL]])
 // CHECK:   Expire (LoanID: [[L_X]])
 }
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_ADDR_X]] contains Loan [[L_X]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_X]]
+// CHECK-DAG: Origin [[O_RET_VAL]] contains Loan [[L_X]]
 
 
 // Pointer Assignment and Return
@@ -42,6 +46,14 @@ MyObj* assign_and_return_local_addr() {
 // CHECK: ReturnOfOrigin (OriginID: [[O_PTR2_RVAL_2]])
 // CHECK: Expire (LoanID: [[L_Y]])
 }
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_ADDR_Y]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR1]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR2]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR1_RVAL]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR1_RVAL_2]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR2_RVAL]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR2_RVAL_2]] contains Loan [[L_Y]]
 
 
 // Return of Non-Pointer Type
@@ -52,6 +64,8 @@ int return_int_val() {
   return x;
 }
 // CHECK-NEXT: End of Block
+// CHECK: Dataflow results:
+// CHECK:  <empty>
 
 
 // Loan Expiration (Automatic Variable, C++)
@@ -64,6 +78,9 @@ void loan_expires_cpp() {
 // CHECK: AssignOrigin (DestID: [[O_POBJ:[0-9]+]], SrcID: [[O_ADDR_OBJ]])
 // CHECK: Expire (LoanID: [[L_OBJ]])
 }
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_ADDR_OBJ]] contains Loan [[L_OBJ]]
+// CHECK-DAG: Origin [[O_POBJ]] contains Loan [[L_OBJ]]
 
 
 // FIXME: No expire for Trivial Destructors
@@ -78,6 +95,9 @@ void loan_expires_trivial() {
 // CHECK-NEXT: End of Block
   // FIXME: Add check for Expire once trivial destructors are handled for expiration.
 }
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_ADDR_TRIVIAL_OBJ]] contains Loan [[L_TRIVIAL_OBJ]]
+// CHECK-DAG: Origin [[O_PTOBJ]] contains Loan [[L_TRIVIAL_OBJ]]
 
 
 // CHECK-LABEL: Function: conditional
@@ -98,6 +118,66 @@ void conditional(bool condition) {
   // CHECK: AssignOrigin (DestID: [[O_P_RVAL:[0-9]+]], SrcID: [[O_P]])
   // CHECK: AssignOrigin (DestID: [[O_Q:[0-9]+]], SrcID: [[O_P_RVAL]])
 }
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_ADDR_A]] contains Loan [[L_A]]
+// CHECK-DAG: Origin [[O_ADDR_B]] contains Loan [[L_B]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_A]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_B]]
+// CHECK-DAG: Origin [[O_Q]] contains Loan [[L_A]]
+// CHECK-DAG: Origin [[O_Q]] contains Loan [[L_B]]
+
+
+// CHECK-LABEL: Function: pointers_in_a_cycle
+void pointers_in_a_cycle(bool condition) {
+  MyObj v1{1};
+  MyObj v2{1};
+  MyObj v3{1};
+
+  MyObj* p1 = &v1;
+  MyObj* p2 = &v2;
+  MyObj* p3 = &v3;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   Issue (LoanID: [[L_V1:[0-9]+]], OriginID: [[O_ADDR_V1:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P1:[0-9]+]], SrcID: [[O_ADDR_V1]])
+// CHECK:   Issue (LoanID: [[L_V2:[0-9]+]], OriginID: [[O_ADDR_V2:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P2:[0-9]+]], SrcID: [[O_ADDR_V2]])
+// CHECK:   Issue (LoanID: [[L_V3:[0-9]+]], OriginID: [[O_ADDR_V3:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P3:[0-9]+]], SrcID: [[O_ADDR_V3]])
+
+  while (condition) {
+    MyObj* temp = p1;
+    p1 = p2;
+    p2 = p3;
+    p3 = temp;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   AssignOrigin (DestID: [[O_P1_RVAL:[0-9]+]], SrcID: [[O_P1]])
+// CHECK:   AssignOrigin (DestID: [[O_TEMP:[0-9]+]], SrcID: [[O_P1_RVAL]])
+// CHECK:   AssignOrigin (DestID: [[O_P2_RVAL:[0-9]+]], SrcID: [[O_P2]])
+// CHECK:   AssignOrigin (DestID: [[O_P1]], SrcID: [[O_P2_RVAL]])
+// CHECK:   AssignOrigin (DestID: [[O_P3_RVAL:[0-9]+]], SrcID: [[O_P3]])
+// CHECK:   AssignOrigin (DestID: [[O_P2]], SrcID: [[O_P3_RVAL]])
+// CHECK:   AssignOrigin (DestID: [[O_TEMP_RVAL:[0-9]+]], SrcID: [[O_TEMP]])
+// CHECK:   AssignOrigin (DestID: [[O_P3]], SrcID: [[O_TEMP_RVAL]])
+  }
+}
+// At the end of the analysis, the origins for the pointers involved in the cycle
+// (p1, p2, p3, temp) should all contain the loans from v1, v2, and v3 at the fixed point.
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_P1]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_P1]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_P1]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_P2]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_P2]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_P2]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_P3]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_P3]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_P3]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_TEMP]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_TEMP]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_TEMP]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_ADDR_V1]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_ADDR_V2]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_ADDR_V3]] contains Loan [[L_V3]]
 
 
 // CHECK-LABEL: Function: overwrite_origin
@@ -114,6 +194,9 @@ void overwrite_origin() {
 // CHECK:   Expire (LoanID: [[L_S2]])
 // CHECK:   Expire (LoanID: [[L_S1]])
 }
+// CHECK: Dataflow results:
+// CHECK:     Origin [[O_P]] contains Loan [[L_S2]]
+// CHECK-NOT: Origin [[O_P]] contains Loan [[L_S1]]
 
 
 // CHECK-LABEL: Function: reassign_to_null
@@ -129,6 +212,8 @@ void reassign_to_null() {
 }
 // FIXME: Have a better representation for nullptr than just an empty origin. 
 //        It should be a separate loan and origin kind.
+// CHECK: Dataflow results:
+// CHECK: Origin [[O_P]] contains no loans
 
 
 // CHECK-LABEL: Function: reassign_in_if
@@ -149,6 +234,102 @@ void reassign_in_if(bool condition) {
 // CHECK:   Expire (LoanID: [[L_S2]])
 // CHECK:   Expire (LoanID: [[L_S1]])
 }
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S2]]
+// CHECK-DAG: Origin [[O_ADDR_S1]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_ADDR_S2]] contains Loan [[L_S2]]
+
+
+// CHECK-LABEL: Function: assign_in_switch
+void assign_in_switch(int mode) {
+  MyObj s1;
+  MyObj s2;
+  MyObj s3;
+  MyObj* p = nullptr;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   AssignOrigin (DestID: [[O_NULLPTR_CAST:[0-9]+]], SrcID: [[O_NULLPTR:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P:[0-9]+]], SrcID: [[O_NULLPTR_CAST]])
+  switch (mode) {
+    case 1:
+      p = &s1;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   Issue (LoanID: [[L_S1:[0-9]+]], OriginID: [[O_ADDR_S1:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_S1]])
+      break;
+    case 2:
+      p = &s2;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   Issue (LoanID: [[L_S2:[0-9]+]], OriginID: [[O_ADDR_S2:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_S2]])
+      break;
+    default:
+      p = &s3;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   Issue (LoanID: [[L_S3:[0-9]+]], OriginID: [[O_ADDR_S3:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_S3]])
+      break;
+  }
+// CHECK: Block B{{[0-9]+}}:
+// CHECK-DAG:   Expire (LoanID: [[L_S3]])
+// CHECK-DAG:   Expire (LoanID: [[L_S2]])
+// CHECK-DAG:   Expire (LoanID: [[L_S1]])
+}
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S2]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S3]]
+// CHECK-DAG: Origin [[O_ADDR_S1]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_ADDR_S2]] contains Loan [[L_S2]]
+// CHECK-DAG: Origin [[O_ADDR_S3]] contains Loan [[L_S3]]
+
+
+// CHECK-LABEL: Function: loan_in_loop
+void loan_in_loop(bool condition) {
+  MyObj* p = nullptr;
+  // CHECK:   AssignOrigin (DestID: [[O_NULLPTR_CAST:[0-9]+]], SrcID: [[O_NULLPTR:[0-9]+]])
+  // CHECK:   AssignOrigin (DestID: [[O_P:[0-9]+]], SrcID: [[O_NULLPTR_CAST]])
+  while (condition) {
+    MyObj inner;
+    p = &inner;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   Issue (LoanID: [[L_INNER:[0-9]+]], OriginID: [[O_ADDR_INNER:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_INNER]])
+// CHECK:   Expire (LoanID: [[L_INNER]])
+  }
+}
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_INNER]]
+// CHECK-DAG: Origin [[O_ADDR_INNER]] contains Loan [[L_INNER]]
+
+
+// CHECK-LABEL: Function: loop_with_break
+void loop_with_break(int count) {
+  MyObj s1;
+  MyObj s2;
+  MyObj* p = &s1;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   Issue (LoanID: [[L_S1:[0-9]+]], OriginID: [[O_ADDR_S1:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P:[0-9]+]], SrcID: [[O_ADDR_S1]])
+  for (int i = 0; i < count; ++i) {
+    if (i == 5) {
+      p = &s2;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   Issue (LoanID: [[L_S2:[0-9]+]], OriginID: [[O_ADDR_S2:[0-9]+]])
+// CHECK: ...
[truncated]

@usx95 usx95 requested review from jvoung and vbvictor July 7, 2025 16:11
@usx95 usx95 force-pushed the users/usx95/lifetime-safety-add-dataflow branch from e870b04 to 8cc690f Compare July 8, 2025 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:analysis clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants